<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="excri" id="excri"><span data-lake-id="u96fbf414" id="u96fbf414">典型回答</span></h1>
  <p data-lake-id="u95c733f1" id="u95c733f1"><br></p>
  <p data-lake-id="u003427e2" id="u003427e2"><span data-lake-id="uffa1cb96" id="uffa1cb96">String是不可变的，StringBuilder和StringBuffer是可变的。而StringBuffer是线程安全的，而StringBuilder是非线程安全的。</span></p>
  <p data-lake-id="u7d8a4796" id="u7d8a4796"><span data-lake-id="ud9466d6c" id="ud9466d6c">​</span><br></p>
  <h1 data-lake-id="WNGzV" id="WNGzV"><span data-lake-id="u19b5c873" id="u19b5c873">扩展知识</span></h1>
  <p data-lake-id="u24f28e21" id="u24f28e21"><br></p>
  <h2 data-lake-id="zW0Lr" id="zW0Lr"><span data-lake-id="u2d685525" id="u2d685525">String的不可变性</span></h2>
  <p data-lake-id="u5493aee0" id="u5493aee0"><br></p>
  <p data-lake-id="u5396ccda" id="u5396ccda"><br></p>
  <h2 data-lake-id="Q3PYJ" id="Q3PYJ"><span data-lake-id="u9200268a" id="u9200268a">为什么设计成不可变的</span></h2>
  <p data-lake-id="ue681f31f" id="ue681f31f"><br></p>
  <h2 data-lake-id="TpASZ" id="TpASZ"><span data-lake-id="u251cd78e" id="u251cd78e">String的"+"是如何实现的</span></h2>
  <p data-lake-id="u5de8f6ef" id="u5de8f6ef"><br></p>
  <p data-lake-id="u48298c95" id="u48298c95" style="text-align: justify"><span data-lake-id="ue6da08d6" id="ue6da08d6" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">使用</span><span data-lake-id="u738e5650" id="u738e5650" class="lake-fontsize-11" style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244)">+</span><span data-lake-id="ub2c3d337" id="ub2c3d337" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">拼接字符串，其实只是Java提供的一个语法糖， 那么，我们就来解一解这个语法糖，看看他的内部原理到底是如何实现的。</span></p>
  <p data-lake-id="u0f381a47" id="u0f381a47" style="text-align: justify"><span data-lake-id="u88a5e95e" id="u88a5e95e" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">还是这样一段代码。我们把他生成的字节码进行反编译，看看结果。</span></p>
  <p data-lake-id="ub69d1a39" id="ub69d1a39"><span data-lake-id="u5c36e7e7" id="u5c36e7e7">​</span><br></p>
  <pre lang="java"><code>
String wechat = "Hollis";
String introduce = "Chuang";
String hollis = wechat + "," + introduce;
</code></pre>
  <p data-lake-id="uac9a21ab" id="uac9a21ab"><span data-lake-id="u1769c633" id="u1769c633">​</span><br></p>
  <p data-lake-id="u57d5b549" id="u57d5b549" style="text-align: justify"><span data-lake-id="ua6b2c4ff" id="ua6b2c4ff" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">反编译后的内容如下，反编译工具为jad。</span></p>
  <p data-lake-id="uf464d608" id="uf464d608" style="text-align: justify"><span data-lake-id="u6cb52b6e" id="u6cb52b6e" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <pre lang="java"><code>
String wechat = "Hollis";
String introduce = "Chuang";
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();
</code></pre>
  <p data-lake-id="u37a8ad76" id="u37a8ad76" style="text-align: justify"><span data-lake-id="u4a0123be" id="u4a0123be" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ua40e8c4b" id="ua40e8c4b" style="text-align: justify"><span data-lake-id="ub75d75d1" id="ub75d75d1" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">通过查看反编译以后的代码，我们可以发现，原来字符串常量在拼接过程中，是将String转成了StringBuilder后，使用其append方法进行处理的。</span></p>
  <p data-lake-id="u08462199" id="u08462199" style="text-align: justify"><span data-lake-id="u365735ed" id="u365735ed" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u3bfe08eb" id="u3bfe08eb" style="text-align: justify"><span data-lake-id="u5e9c245f" id="u5e9c245f">那么也就是说，Java中的+对字符串的拼接，其实现原理是使用StringBuilder.append。</span></p>
  <p data-lake-id="u4d2ccecb" id="u4d2ccecb" style="text-align: justify"><span data-lake-id="u283ff985" id="u283ff985">​</span><br></p>
  <p data-lake-id="u2df1930b" id="u2df1930b" style="text-align: justify"><span data-lake-id="u64a2e92e" id="u64a2e92e">​</span><br></p>
  <h2 data-lake-id="d7n3j" id="d7n3j"><span data-lake-id="u14da010f" id="u14da010f">StringBuffer和StringBuilder</span></h2>
  <p data-lake-id="u2625b6e1" id="u2625b6e1"><br></p>
  <p data-lake-id="u6c32d682" id="u6c32d682"><span data-lake-id="uda59edcb" id="uda59edcb">接下来我们看看</span><code data-lake-id="u19a257cc" id="u19a257cc"><span data-lake-id="u2ff37ed1" id="u2ff37ed1">StringBuffer</span></code><span data-lake-id="u7d28950d" id="u7d28950d">和</span><code data-lake-id="u2b742e6b" id="u2b742e6b"><span data-lake-id="u3533f46c" id="u3533f46c">StringBuilder</span></code><span data-lake-id="u9b4fe650" id="u9b4fe650">的实现原理。</span></p>
  <p data-lake-id="uc3b0cbb2" id="uc3b0cbb2"><span data-lake-id="udca2ae2e" id="udca2ae2e">​</span><br></p>
  <p data-lake-id="u78c1a3b2" id="u78c1a3b2"><span data-lake-id="uea193d26" id="uea193d26">和</span><code data-lake-id="uf16354c3" id="uf16354c3"><span data-lake-id="u6da98dba" id="u6da98dba">String</span></code><span data-lake-id="u60a35816" id="u60a35816">类类似，</span><code data-lake-id="u8facc1ef" id="u8facc1ef"><span data-lake-id="uea93c519" id="uea93c519">StringBuilder</span></code><span data-lake-id="u8e0bd0b6" id="u8e0bd0b6">类也封装了一个字符数组，定义如下：</span></p>
  <p data-lake-id="u9457a5c9" id="u9457a5c9"><span data-lake-id="u7fd9a2b5" id="u7fd9a2b5">​</span><br></p>
  <pre lang="java"><code>
char[] value;
</code></pre>
  <p data-lake-id="u8f51eb4a" id="u8f51eb4a"><span data-lake-id="u52b8f639" id="u52b8f639">​</span><br></p>
  <p data-lake-id="ub3fd989c" id="ub3fd989c"><span data-lake-id="u1de300ba" id="u1de300ba">​</span><br></p>
  <p data-lake-id="uc109d2be" id="uc109d2be"><span data-lake-id="ub0d7498f" id="ub0d7498f">与</span><code data-lake-id="uba7def9b" id="uba7def9b"><span data-lake-id="u45499d01" id="u45499d01">String</span></code><span data-lake-id="uabdc916f" id="uabdc916f">不同的是，它并不是</span><code data-lake-id="uacfbae99" id="uacfbae99"><span data-lake-id="ue33daa0e" id="ue33daa0e">final</span></code><span data-lake-id="ub01b0e3d" id="ub01b0e3d">的，所以他是可以修改的。另外，与</span><code data-lake-id="ufd9dee3f" id="ufd9dee3f"><span data-lake-id="ue44a85a8" id="ue44a85a8">String</span></code><span data-lake-id="u9b0b3f34" id="u9b0b3f34">不同，字符数组中不一定所有位置都已经被使用，它有一个实例变量，表示数组中已经使用的字符个数，定义如下：</span></p>
  <p data-lake-id="u5a66009c" id="u5a66009c"><span data-lake-id="u2a7e9df6" id="u2a7e9df6">​</span><br></p>
  <pre lang="java"><code>
int count;
</code></pre>
  <p data-lake-id="ucc12866b" id="ucc12866b"><span data-lake-id="u313b8a8c" id="u313b8a8c">​</span><br></p>
  <p data-lake-id="u0bfe520c" id="u0bfe520c"><span data-lake-id="u95d33720" id="u95d33720">其append源码如下：</span></p>
  <p data-lake-id="u327cf32a" id="u327cf32a"><span data-lake-id="u821c924e" id="u821c924e">​</span><br></p>
  <pre lang="java"><code>
public StringBuilder append(String str) {
    super.append(str);
    return this;
}
</code></pre>
  <p data-lake-id="u54c2fd24" id="u54c2fd24"><br></p>
  <p data-lake-id="u1283187e" id="u1283187e"><span data-lake-id="u4d4d6705" id="u4d4d6705">该类继承了`AbstractStringBuilder`类，看下其`append`方法：</span></p>
  <p data-lake-id="u8257d990" id="u8257d990"><span data-lake-id="u71509b64" id="u71509b64">​</span><br></p>
  <pre lang="java"><code>
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
</code></pre>
  <p data-lake-id="u5f06a293" id="u5f06a293"><span data-lake-id="ua75af4b5" id="ua75af4b5">​</span><br></p>
  <p data-lake-id="ub3e9c0d5" id="ub3e9c0d5"><span data-lake-id="u712ca862" id="u712ca862">​</span><br></p>
  <p data-lake-id="u94a2f430" id="u94a2f430"><span data-lake-id="uc973e097" id="uc973e097">append会直接拷贝字符到内部的字符数组中，如果字符数组长度不够，会进行扩展。</span></p>
  <p data-lake-id="u75057437" id="u75057437"><span data-lake-id="u7c38783b" id="u7c38783b">​</span><br></p>
  <p data-lake-id="u20abee97" id="u20abee97"><code data-lake-id="u8d4cda60" id="u8d4cda60"><span data-lake-id="u137c8922" id="u137c8922">StringBuffer</span></code><span data-lake-id="u4fd5a079" id="u4fd5a079">和</span><code data-lake-id="ufdf155f8" id="ufdf155f8"><span data-lake-id="u1981c91a" id="u1981c91a">StringBuilder</span></code><span data-lake-id="u9affaa71" id="u9affaa71">类似，最大的区别就是</span><code data-lake-id="u0906c054" id="u0906c054"><span data-lake-id="ue25d7091" id="ue25d7091">StringBuffer</span></code><span data-lake-id="uc27b1d28" id="uc27b1d28">是线程安全的，看一下</span><code data-lake-id="u2ae2864e" id="u2ae2864e"><span data-lake-id="u4f087df5" id="u4f087df5">StringBuffer</span></code><span data-lake-id="u67d4926e" id="u67d4926e">的`append`方法。</span></p>
  <p data-lake-id="uaddc2cae" id="uaddc2cae"><span data-lake-id="uc6f08baa" id="uc6f08baa">​</span><br></p>
  <pre lang="java"><code>
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
</code></pre>
  <p data-lake-id="u4120f7ed" id="u4120f7ed"><br></p>
  <p data-lake-id="uc94743f9" id="uc94743f9"><span data-lake-id="ubd424ef9" id="ubd424ef9">该方法使用</span><code data-lake-id="u5d64e6f2" id="u5d64e6f2"><span data-lake-id="u50cd6d98" id="u50cd6d98">synchronized</span></code><span data-lake-id="u7384a062" id="u7384a062">进行声明，说明是一个线程安全的方法。而</span><code data-lake-id="u88192424" id="u88192424"><span data-lake-id="u93ff4b49" id="u93ff4b49">StringBuilder</span></code><span data-lake-id="u8e772068" id="u8e772068">则不是线程安全的。</span></p>
  <p data-lake-id="u5d8b4a8e" id="u5d8b4a8e"><span data-lake-id="u72ce5859" id="u72ce5859">​</span><br></p>
  <h2 data-lake-id="rBBk1" id="rBBk1"><span data-lake-id="u7d082eac" id="u7d082eac">不要在for循环中使用+拼接字符串</span></h2>
  <p data-lake-id="u2be52b50" id="u2be52b50"><br></p>
  <p data-lake-id="ucfae9719" id="ucfae9719"><br></p>
  <p data-lake-id="ub77ba3d7" id="ub77ba3d7"><span data-lake-id="u6000172c" id="u6000172c">前面我们分析过，其实使用`+`拼接字符串的实现原理也是使用的`StringBuilder`，那为什么不建议大家在for循环中使用呢？</span></p>
  <p data-lake-id="ue9527422" id="ue9527422"><span data-lake-id="u4b06b0fb" id="u4b06b0fb">​</span><br></p>
  <pre lang="java"><code>
我们把以下代码反编译下：

long t1 = System.currentTimeMillis();
String str = "hollis";
for (int i = 0; i &lt; 50000; i++) {
    String s = String.valueOf(i);
    str += s;
}
long t2 = System.currentTimeMillis();
System.out.println("+ cost:" + (t2 - t1));
</code></pre>
  <p data-lake-id="ud6c00e38" id="ud6c00e38"><span data-lake-id="u34be9d79" id="u34be9d79">​</span><br></p>
  <p data-lake-id="u62850ae4" id="u62850ae4"><span data-lake-id="u3c304e97" id="u3c304e97">​</span><br></p>
  <p data-lake-id="u880f7128" id="u880f7128"><span data-lake-id="u3ac04f42" id="u3ac04f42">反编译后代码如下：</span></p>
  <p data-lake-id="u7d3bae2e" id="u7d3bae2e"><span data-lake-id="u0a3d5735" id="u0a3d5735">​</span><br></p>
  <pre lang="java"><code>
long t1 = System.currentTimeMillis();
String str = "hollis";
for(int i = 0; i &lt; 50000; i++)
{
    String s = String.valueOf(i);
    str = (new StringBuilder()).append(str).append(s).toString();
}

long t2 = System.currentTimeMillis();
System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());
</code></pre>
  <p data-lake-id="u4e0e2f32" id="u4e0e2f32"><br></p>
  <p data-lake-id="uf6b1ce5d" id="uf6b1ce5d"><span data-lake-id="ub3349e3d" id="ub3349e3d">我们可以看到，反编译后的代码，在</span><code data-lake-id="u07d3513f" id="u07d3513f"><span data-lake-id="ue82c7b44" id="ue82c7b44">for</span></code><span data-lake-id="u4bf278cf" id="u4bf278cf">循环中，每次都是</span><code data-lake-id="uc4934a08" id="uc4934a08"><span data-lake-id="ud9e41434" id="ud9e41434">new</span></code><span data-lake-id="ub4297ca6" id="ub4297ca6">了一个</span><code data-lake-id="u8013ef28" id="u8013ef28"><span data-lake-id="u059dbf3f" id="u059dbf3f">StringBuilder</span></code><span data-lake-id="u0f45f1a3" id="u0f45f1a3">，然后再把</span><code data-lake-id="ucff827d9" id="ucff827d9"><span data-lake-id="ua3341aac" id="ua3341aac">String</span></code><span data-lake-id="uab3f1ac2" id="uab3f1ac2">转成</span><code data-lake-id="u3671f780" id="u3671f780"><span data-lake-id="u1dcba05e" id="u1dcba05e">StringBuilder</span></code><span data-lake-id="u82e621c1" id="u82e621c1">，再进行</span><code data-lake-id="ud99e6532" id="ud99e6532"><span data-lake-id="u8d9c64a8" id="u8d9c64a8">append</span></code><span data-lake-id="u4e13a6f0" id="u4e13a6f0">。</span></p>
  <p data-lake-id="u7bfc59ca" id="u7bfc59ca"><span data-lake-id="u122bfeeb" id="u122bfeeb">​</span><br></p>
  <p data-lake-id="u54e9f87e" id="u54e9f87e"><span data-lake-id="ue9aee7dc" id="ue9aee7dc">而频繁的新建对象当然要耗费很多时间了，不仅仅会耗费时间，频繁的创建对象，还会造成内存资源的浪费。</span></p>
  <p data-lake-id="u007d5de9" id="u007d5de9"><span data-lake-id="u986bae40" id="u986bae40">​</span><br></p>
  <p data-lake-id="u8fc88b1c" id="u8fc88b1c"><span data-lake-id="ue7bcbdcc" id="ue7bcbdcc">所以，阿里巴巴Java开发手册建议：循环体内，字符串的连接方式，使用 </span><code data-lake-id="ufa9612b5" id="ufa9612b5"><span data-lake-id="u9b3aa66e" id="u9b3aa66e">StringBuilder</span></code><span data-lake-id="u394b3ab2" id="u394b3ab2"> 的 </span><code data-lake-id="u98af1fb1" id="u98af1fb1"><span data-lake-id="u9bfed0a8" id="u9bfed0a8">append</span></code><span data-lake-id="u6f0b422d" id="u6f0b422d"> 方法进行扩展。而不要使用</span><code data-lake-id="uc03534f4" id="uc03534f4"><span data-lake-id="u36413e30" id="u36413e30">+</span></code><span data-lake-id="u79d77bcf" id="u79d77bcf">。</span></p>
 </body>
</html>